#include "common.h"
#include "msn.h"
#include "msn_contacts.h"
#include "msn_soap2.h"
#include "sock.h"
#include "msn_user.h"

static const char *contact_domain = MSN_CONTACT_SERVER;
static const char *membership_path_template = PREFIX"/share/%s_membership.xml";
static const char *contact_list_path_template = PREFIX"/share/%s_contact.xml";

static char *path_get(const char *fmt, ...);
/* contact */
static void msn_contact_add_connected(int fd, int error, int res, void *data);
static void msn_contact_add_request_done(int fd, int error, int res, void *data);
static int msn_contact_add_filter(const char *buf, const int size, void *data);

static void msn_contact_connected(int fd, int error, int res, void *data);
static void msn_contact_list_read_done(msn_addr_book_t *ab, int error);
static int msn_contact_list_xml_parse(const char *path, msn_session_t *robot);
static void msn_contact_list_read(int fd, short flags, void *data);
static void msn_contact_list_request_finish(int fd, int error, int res, void *data);
/* membership */
static void msn_membership_connected(int fd, int error, int res, void *data);
static void msn_membership_request_finish(int fd, int error, int res, void *data);
static int msn_membership_filter(const char *buf, const int size, void *data);
static void msn_membership_filter_done(msn_addr_book_t *ab, int error);
static int msn_membership_xml_parse(const char *path, msn_session_t *robot);
static void msn_membership_node_parse(xmlNodePtr p, msn_session_t *ms);
/* adl */
static void msn_adl_xml_friend_insert(xmlDocPtr doc, xmlNodePtr root, const char *quick, \
				const char *domain);

void msn_contact_list_get(msn_session_t *ms)
{
	msn_addr_book_t *ab = NULL;
	struct sockaddr_in remote;

	if(!ms)
		return;
	ab = msn_addr_book_init(ms);
	if(!ab)
		return;
	debug_log(9, "%s\n", "begin to get contact list");
	remote.sin_family = AF_INET;
	remote.sin_port = htons(80);
	remote.sin_addr = ab->addr;
	sock_connect(ab->ev, ab->fd, &remote, ab, msn_contact_connected);
}

static void msn_contact_list_request_finish(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;

	if(error) {
		error_log(errno, "failed to send contact list request\n");
		return;
	}
	assert(ab);
	debug_log(9, "fd %d, %d bytes send finish\n", fd, res);
	xevent_update(ab->ev, SOCK_FLAG_READ, ab, msn_contact_list_read);
}

msn_addr_book_t *msn_addr_book_init(msn_session_t *robot)
{
	msn_addr_book_t *ab = g_slice_new(msn_addr_book_t);
	if(!ab)
		return NULL;
	ab->ms = robot;
	ab->fd = sock_open_from_host(contact_domain, &ab->addr);
	ab->ev = xevent_new();
	xevent_set(ab->ev, ab->fd, SOCK_FLAG_WRITE, NULL, NULL);
	xevent_base_set(ab->ev, master_base);

	return ab;
}

void msn_addr_book_free(msn_addr_book_t *cl)
{
	if(!cl)
		return;
	sock_close(cl->fd);
	xevent_clean(cl->ev);
	http_reply_header_clean(cl->rh);
	fclose(cl->out);
	g_slice_free(msn_addr_book_t, cl);
}

static void msn_contact_connected(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;
	msn_session_t *robot = NULL;
	GString *soap = NULL;
	security_token_t *token = NULL;
	
	if(error) {
		error_log(errno, "failed to connecte contact server\n");
		return;
	}
	assert(ab && ab->ms);
	robot = ab->ms;
	debug_log(9, "fd %d connected with remote server\n", fd);
	token = security_token_find_by_domain(robot->sec_tokens, contact_domain);
	if(!token)
		return;
	soap = msn_contact_get_soap_gen(token->ticket->str, "0001-01-01T00:00:00.0000000-08:00");
	assert(soap);
	sock_write(ab->ev, ab->fd, msn_contact_list_request_finish, ab, soap);
	g_string_free(soap, 1);
}

static void msn_contact_list_read(int fd, short flags, void *data)
{
	msn_addr_book_t *ab = data;
	char readbuf[MAX_BUFFER];
	int size_read = -1;

	assert(ab);
	if((size_read = read(fd, readbuf, MAX_BUFFER)) == -1) {
		error_log(errno, "fd %d read failed\n", fd);
		msn_contact_list_read_done(ab, 1);
		return ;
	}
	if(size_read == 0) {
		/* connection has been reset, how to do */
		debug_log(5, "%s close connection %d\n", contact_domain, fd);
		msn_contact_list_read_done(ab, 0);
		return;
	}
	ab->in += size_read;
	debug_log(9, "%d bytes in, total in %d\n", size_read, ab->in);
	if(!ab->out) {
		char path[128];
		g_assert(ab->ms && ab->ms->name);
		snprintf(path, 127, contact_list_path_template, ab->ms->name->str);
		ab->out = fopen(path, "w");
		if(!ab->out) {
			error_log(errno, "failed to open %s\n", path);
		}
	}
	assert(ab->out);
	if(!ab->header_parsed) {
		ab->rh = http_reply_header_parse(readbuf, size_read);
		assert(ab->rh);
		ab->header_size = headers_end(readbuf, size_read);
		ab->header_parsed = 1;
		header_entry_t *en = header_entry_get_by_id(ab->rh->headers, HDR_CONTENT_LENGTH);
		if(en) {
			ab->total = ab->header_size + atoi(en->value);
		}
		debug_log(6, "Total %d, in %d, header size %d\n", ab->total, ab->in, ab->header_size);
		if(ab->rh->statu != HTTP_OK) {
			debug_log(0, "sorry! %s response %d\n", contact_domain, ab->rh->statu);
		}
		if(1 != fwrite(readbuf + ab->header_size, size_read - ab->header_size, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	else {
		if(1 != fwrite(readbuf, size_read, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	if(ab->in >= ab->total) 
		msn_contact_list_read_done(ab, 0);
}

static void msn_contact_list_read_done(msn_addr_book_t *ab, int error)
{
	assert(ab);
	char *path = NULL;
	msn_session_t *robot = ab->ms;
	int ok = 0;

	if(error) {
		msn_addr_book_free(ab);
		return ;
	}

	ok = (ab->rh->statu == HTTP_OK) ? 1 : 0;
	if(!ok) {
		/* reset robot */
		msn_addr_book_free(ab);
		return ;
	}
	msn_addr_book_free(ab);
	path = path_get(contact_list_path_template, robot->name->str);
	/* parse contact list xml */
	if(-1 == msn_contact_list_xml_parse(path, robot)) {
		/* clean robot */
		msn_session_clean(robot);
		return;
	}
	msn_adl_send(robot);
}

static int msn_contact_list_xml_parse(const char *path, msn_session_t *robot)
{
	xmlDocPtr doc;
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	int i = 0;
	
	if(!path || !robot) 
		return -1;
	doc = xmlReadFile(path, "UTF-8", 0);
	if(!doc) {
		debug_log(0, "failed to parse %s\n", path);
		return -1;
	}
	context = xmlXPathNewContext(doc);
	if (context == NULL) {   
		debug_log(0, "%s", "failed to create context\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	xmlXPathRegisterNs(context, "soap", "http://schemas.xmlsoap.org/soap/envelope/");
	xmlXPathRegisterNs(context, "x", "http://www.msn.com/webservices/AddressBook");
	result = xmlXPathEvalExpression(\
			BAD_CAST "//x:ABFindAllResponse/x:ABFindAllResult/x:contacts/x:Contact",\
		   	context);
	if(result == NULL) {
		debug_log(0, "%s", "failed to exec xpath\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
		debug_log(0, "%s", "node set is empty\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	/* foreach node set */
	for(i = 0; i < result->nodesetval->nodeNr; i++) {
		msn_user_t *fri = NULL;
		xmlNodePtr node = result->nodesetval->nodeTab[i];
		xmlNodePtr children;
		
		fri = msn_user_new(NULL);
		assert(fri);
		for(children = node->children; children; children = children->next) {
			debug_log(9, "%d %s\n", i, children->name);
			xmlNodePtr children1;
			xmlChar *temp = NULL;

			if(!strcasecmp(children->name, "contactInfo")) {
				for(children1 = children->children; children1; children1 = children1->next) {
					debug_log(9, "%d %s:%s\n", i, children->name, children1->name);
					if(!strcasecmp(children1->name, "passportName")) {
						temp = xmlNodeGetContent(children1);
						fri->passport = g_string_new_len(temp, xmlStrlen(temp));
						debug_log(8, "%d %s:%s %s\n", i, children->name, \
										children1->name, fri->passport->str);
					}
					if(!strcasecmp(children1->name, "displayName")) {
						temp = xmlNodeGetContent(children1);
						fri->display_name = g_string_new_len(temp, xmlStrlen(temp));
						debug_log(8, "%d %s:%s %s\n", i, children->name, \
										children1->name, fri->display_name->str);
					}
				}
				break;
			}
		}
		if(fri->passport) {
			if(g_hash_table_lookup(robot->friends, fri->passport))
				msn_user_clean(fri);
			else 
				/* append friends */
				g_hash_table_insert(robot->friends, fri->passport, fri);
		}
	}

	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	xmlFreeDoc(doc);
	return 0;
}

GString *msn_adl_xml_gen(msn_session_t *ms)
{
	GHashTable *friends = ms->friends;
	xmlDocPtr doc;
	xmlNodePtr ml;
	xmlChar *xml;
	int xml_len;
	int i = 0;
	GHashTableIter iter;
	gpointer key, value;

	g_return_val_if_fail(ms != NULL && ms->friends != NULL, NULL);
	g_hash_table_iter_init (&iter, friends);
	doc = xmlNewDoc(NULL);
	ml = xmlNewDocNode(doc, NULL, "ml", NULL);
	assert(ml);
	xmlDocSetRootElement(doc, ml);
	while(g_hash_table_iter_next (&iter, &key, &value)) {
		/* do something with key and value */
		msn_user_t *user = value;
		g_assert(user->passport);
		char *quick = NULL;
		char *domain = strchr(user->passport->str, '@');
		if(domain)  {
			quick = g_strndup(user->passport->str, domain - user->passport->str);
			domain++;
		}
		debug_log(9, "Have insert %d, %s %s ready\n", i++, quick, domain);
		msn_adl_xml_friend_insert(doc, ml, quick, domain);
		g_free(quick);
	}
	xmlDocDumpMemoryEnc(doc, &xml, &xml_len, "UTF-8");
	/* format string */
	int first_line = 0;
	xmlChar *start;

	start = strstr(xml, "<ml>");
	first_line = start - xml;
	while(isspace(*(xml + xml_len - 1))) xml_len--;
	GString *r = g_string_new_len(xml + first_line, xml_len - first_line);
	if(!r)
		return NULL;
	debug_log(9, "ADL %d bytes {%s}\n", r->len, r->str);
	/*free the document */
	xmlFreeDoc(doc);
	xmlCleanupParser();
	xmlMemoryDump();
	return r;
}
		
static void msn_adl_xml_friend_insert(xmlDocPtr doc, xmlNodePtr root, const char *quick, \
				const char *domain)
{
	if(!doc)
		return;
	assert(quick && domain);
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	char exp[MAX_BUFFER];
	int i = 0;
	
	context = xmlXPathNewContext(doc);
	snprintf(exp, MAX_BUFFER - 1, "/ml/d[@n='%s']", domain);
	if (context == NULL) {   
		debug_log(0, "%s", "failed to create context\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		return;
	}
	result = xmlXPathEvalExpression(\
			BAD_CAST exp,\
		   	context);
	if(result == NULL) {
		debug_log(0, "%s", "failed to exec xpath\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		return;
	}
	debug_log(9, "find %d nodes\n", result->nodesetval->nodeNr);
	if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
		debug_log(0, "%s", "node set is empty\n");
		xmlNodePtr d = xmlNewNode(NULL, BAD_CAST "d");
		xmlNewProp(d, "n", domain);
		xmlAddChild(root, d);
		xmlNodePtr c = xmlNewNode(NULL, "c");
		xmlNewProp(c, "n", quick);
		xmlNewProp(c, "l", "2");
		xmlNewProp(c, "t", "1");
		xmlAddChild(d, c);
	}
	else {
		xmlNodePtr d = result->nodesetval->nodeTab[0];
		xmlNodePtr c = xmlNewNode(NULL, "c");
		xmlNewProp(c, "n", quick);
		xmlNewProp(c, "l", "3");
		xmlNewProp(c, "t", "1");
		xmlAddChild(d, c);
	}
	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	debug_log(9, "insert friend %s, domain %s\n", quick, domain);
}

msn_user_t * msn_adl_xml_parse(msn_session_t *robot, const char *buf, const int size)
{
	if(!robot || !buf)
		return NULL;

	msn_user_t *fri = NULL;
	xmlDocPtr doc;
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	char exp[MAX_BUFFER];
	int i = 0;
	
	doc = xmlReadMemory(buf, size, NULL, "UTF-8", 0);
	if(!doc) {
		debug_log(0, "%s", "failed to create doc\n)");
		return NULL;
	}
	context = xmlXPathNewContext(doc);
	snprintf(exp, MAX_BUFFER - 1, "/ml/d/c");
	if (context == NULL) {   
		debug_log(0, "%s", "failed to create context\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		return NULL;
	}
	result = xmlXPathEvalExpression(\
			BAD_CAST exp,\
		   	context);
	if(result == NULL) {
		debug_log(0, "%s", "failed to exec xpath\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		return NULL;
	}
	debug_log(9, "find %d nodes\n", result->nodesetval->nodeNr);
	if (!xmlXPathNodeSetIsEmpty(result->nodesetval)) {
		xmlNodePtr c = result->nodesetval->nodeTab[0];
		xmlNodePtr d = c->parent;
		xmlChar *tmp = xmlGetProp(d, "n");
		g_assert(tmp);
		xmlChar *domain = g_strdup(tmp);
		fri = msn_user_new(NULL);

		/* add passport */
		fri->passport = g_string_new(NULL);
		tmp = xmlGetProp(c, "n");
		g_string_printf(fri->passport, "%s@%s",  tmp, domain);
		/* display name */
		tmp = xmlGetProp(c, "f");
		fri->display_name = g_string_new_len(tmp, xmlStrlen(tmp));
		g_free(domain);
		/* hash check */
		if(!g_hash_table_lookup(robot->friends, fri->passport)) {
			debug_log(3, "%s %s\n", fri->passport->str, fri->display_name->str);
			g_hash_table_insert(robot->friends, fri->passport, fri);
		}
		else 
			msn_user_clean(fri);
	}
	xmlFreeDoc(doc);
	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	return fri;
}

void msn_membership_get(msn_session_t *robot)
{
	msn_addr_book_t *ab = NULL;
	struct sockaddr_in remote;

	if(!robot)
		return;
	ab = msn_addr_book_init(robot);
	if(!ab)
		return;
	debug_log(9, "%s\n", "begin to get membership");
	remote.sin_family = AF_INET;
	remote.sin_port = htons(80);
	remote.sin_addr = ab->addr;
	sock_connect(ab->ev, ab->fd, &remote, ab, msn_membership_connected);
}

static int msn_membership_filter(const char *buf, const int size, void *data)
{
	msn_addr_book_t *ab = data;
	char *path;

	assert(ab);
	if(!buf) {
		/* read finish */
		msn_membership_filter_done(ab, 0);
		return;
	}
	g_assert(ab->ms && ab->ms->name);
	path = path_get(membership_path_template, ab->ms->name->str);
	ab->in += size;
	debug_log(9, "%d bytes in, total in %d\n", size, ab->in);
	if(!ab->out) {
		ab->out = fopen(path, "w");
		if(!ab->out) {
			error_log(errno, "failed to open %s\n", path);
		}
	}
	assert(ab->out);
	if(!ab->header_parsed) {
		ab->rh = http_reply_header_parse(buf, size);
		assert(ab->rh);
		ab->header_size = headers_end(buf, size);
		ab->header_parsed = 1;
		header_entry_t *en = header_entry_get_by_id(ab->rh->headers, HDR_CONTENT_LENGTH);
		if(en) {
			ab->total = ab->header_size + atoi(en->value);
		}
		debug_log(6, "Total %d, in %d, header size %d\n", ab->total, ab->in, ab->header_size);
		if(ab->rh->statu != HTTP_OK) {
			debug_log(1, "sorry! %s response %d\n", contact_domain, ab->rh->statu);
		}
		if(1 != fwrite(buf + ab->header_size, size - ab->header_size, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	else {
		if(1 != fwrite(buf, size, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	if(ab->in >= ab->total) 
		msn_membership_filter_done(ab, 0);
}

static void msn_membership_connected(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;
	msn_session_t *robot = NULL;
	GString *soap = NULL;
	security_token_t *token = NULL;
	
	if(error) {
		error_log(errno, "failed to connect contact server\n");
		return;
	}
	assert(ab && ab->ms);
	robot = ab->ms;
	debug_log(9, "fd %d connected with remote server\n", fd);
	token = security_token_find_by_domain(robot->sec_tokens, contact_domain);
	if(!token)
		return;
	g_assert(token->ticket);
	soap = msn_membership_get_soap_gen(token->ticket->str, "0001-01-01T00:00:00.0000000-08:00");
	if(!soap)
		return;
	sock_write(ab->ev, ab->fd, msn_membership_request_finish, ab, soap);
	g_string_free(soap, 1);
}

static void msn_membership_request_finish(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;

	if(error) {
		error_log(errno, "failed to send contact list request\n");
		msn_addr_book_free(ab);
		return;
	}
	assert(ab);
	debug_log(9, "fd %d, %d bytes send finish\n", fd, res);
	sock_read(ab->ev, ab->fd, msn_membership_filter, ab);
}

static void msn_membership_filter_done(msn_addr_book_t *ab, int error)
{
	assert(ab);
	if(error) {
		msn_addr_book_free(ab);
		return ;
	}

	msn_session_t *robot = ab->ms;
	char *path;
	int ok = 0;

	path = path_get(membership_path_template, robot->name->str);
	ok = (ab->rh->statu == HTTP_OK) ? 1 : 0;
	if(!ok) {
		msn_addr_book_free(ab);
		return ;
	}
	msn_addr_book_free(ab);
	/* parse membership xml */
	if(-1 == msn_membership_xml_parse(path, robot)) {
		debug_log(0, "failed to parse %s\n", path);
		return;
	}
	/* call function to send ADL */
	msn_contact_list_get(robot);
}

static int msn_membership_xml_parse(const char *path, msn_session_t *robot)
{
	xmlDocPtr doc;
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;
	xmlNodePtr pending_node = NULL;
	int i = 0;
	
	if(!path || !robot) 
		return -1;
	doc = xmlReadFile(path, "UTF-8", 0);
	if(!doc) {
		debug_log(0, "failed to parse %s\n", path);
		return -1;
	}
	context = xmlXPathNewContext(doc);
	if (context == NULL) {   
		debug_log(0, "%s", "failed to create context\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	xmlXPathRegisterNs(context, "soap", "http://schemas.xmlsoap.org/soap/envelope/");
	xmlXPathRegisterNs(context, "x", "http://www.msn.com/webservices/AddressBook");
	result = xmlXPathEvalExpression(\
			BAD_CAST "//x:FindMembershipResponse/x:FindMembershipResult/x:Services/x:Service/x:Memberships/x:Membership",\
		   	context);
	if(result == NULL) {
		debug_log(0, "%s", "failed to exec xpath\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
		debug_log(0, "%s", "node set is empty\n");
		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(doc);
		return -1;
	}
	/* foreach membership set */
	for(i = 0; i < result->nodesetval->nodeNr; i++) {
		xmlNodePtr c1 = result->nodesetval->nodeTab[i];
		msn_membership_node_parse(c1, robot);
	}

	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	xmlFreeDoc(doc);
	return 0;
}

static char *path_get(const char *fmt, ...)
{
	va_list va;
	static char buf[128];

	va_start(va, fmt);
	vsnprintf(buf, 127, fmt, va);
	va_end(va);
	return buf;
}

GString *msn_adl_xml_gen_one(msn_user_t *user)
{
	xmlDocPtr doc;
	xmlNodePtr ml;
	xmlChar *xml;
	int xml_len;
	int i = 0;
	char *quick = NULL;
	char *domain;

	g_return_val_if_fail(user != NULL, NULL);
	doc = xmlNewDoc(NULL);
	ml = xmlNewDocNode(doc, NULL, "ml", NULL);
	assert(ml);
	xmlDocSetRootElement(doc, ml);
	g_assert(user->passport);
	domain = strchr(user->passport->str, '@');
	if(domain)  {
		quick = g_strndup(user->passport->str, domain - user->passport->str);
		domain++;
	}
	debug_log(9, "Have insert %d, %s %s ready\n", i++, quick, domain);
	msn_adl_xml_friend_insert(doc, ml, quick, domain);
	g_free(quick);
	xmlDocDumpMemoryEnc(doc, &xml, &xml_len, "UTF-8");
	/* format string */
	int first_line = 0;
	xmlChar *start;

	start = strstr(xml, "<ml>");
	first_line = start - xml;
	while(isspace(*(xml + xml_len - 1))) xml_len--;
	
	GString *r = g_string_new_len(xml + first_line, xml_len - first_line);
	if(!r)
		return NULL;
	debug_log(9, "ADL %d bytes {%s}\n", r->len, r->str);
	/*free the document */
	xmlFreeDoc(doc);
	xmlCleanupParser();
	xmlMemoryDump();
	return r;
}

static void msn_membership_node_parse(xmlNodePtr p, msn_session_t *ms)
{
	g_return_if_fail(p!=NULL && ms!=NULL);
	
	xmlNodePtr c;
	msn_user_t *user;
	MSN_USER_LIST_TYPE list_type;
	xmlChar *tmp;
	security_token_t *token = NULL;
	
	token = security_token_find_by_domain(ms->sec_tokens, contact_domain);
	g_assert(token);
	for(c = p->children; c; c = c->next) {
		xmlNodePtr c1;

		debug_log(8, "%s\n", c->name);
		if(!strcasecmp("MemberRole", c->name)) {
			tmp = xmlNodeGetContent(c);
			list_type = msn_user_list_type_get(tmp);
			debug_log(8, "%s %s\n", c->name, tmp);
		}
		else if(!strcasecmp("Members", c->name)) {
			xmlNodePtr c1 ;
			
			for(c1 = c->children; c1; c1 = c1->next) {
				if(!strcasecmp(c1->name, "Member")) {
					user = msn_user_new(NULL);
					xmlNodePtr c2;
					
					for(c2 = c1->children; c2; c2 = c2->next) {
						if(!strcasecmp(c2->name, "PassportName")){
							tmp = xmlNodeGetContent(c2);
							user->passport = g_string_new(tmp);
						}
					}
					g_assert(user->passport);
					/* insert to hashtable */
					msn_user_t *old = g_hash_table_lookup(ms->friends, user->passport);
					if(old) {
						if(!g_list_find(old->list_type, GINT_TO_POINTER(list_type))) 
							old->list_type = g_list_append(old->list_type , GINT_TO_POINTER(list_type));
						msn_user_clean(user);
					}
					else {
						g_hash_table_insert(ms->friends, user->passport, user);
						debug_log(9, "Insert %s %s\n", msn_user_list_type_string(list_type), \
										user->passport->str);
						if(list_type == MSN_USER_LIST_PL) {
							msn_contact_add(user, token->ticket->str, MSN_USER_LIST_AL);
						}
					}
				}
			}
		}
	}
}

void msn_contact_add(msn_user_t *user, const char *ticket, MSN_USER_LIST_TYPE type)
{
	msn_addr_book_t *ab = NULL;
	struct sockaddr_in remote;
	GString *out = NULL;

	g_return_if_fail(user!=NULL && ticket!=NULL);
	ab = msn_addr_book_init(NULL);
	if(!ab)
		return;
	debug_log(9, "%s\n", "begin to add %s\n", user->passport->str);
	remote.sin_family = AF_INET;
	remote.sin_port = htons(80);
	remote.sin_addr = ab->addr;
	out = msn_contact_add_soap_gen(ticket, user->passport->str, msn_user_list_type_string(type));
	if(!out)
		return;
	ab->data = out;
	sock_connect(ab->ev, ab->fd, &remote, ab, msn_contact_add_connected);
}

static void msn_contact_add_connected(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;
	GString *soap = NULL;
	
	if(error) {
		error_log(errno, "failed to connecte contact server\n");
		return;
	}
	debug_log(9, "fd %d connected with remote server\n", fd);
	soap = ab->data;
	g_assert(soap);
	sock_write(ab->ev, ab->fd, msn_contact_add_request_done, ab, soap);
	g_string_free(soap, 1);
}

static void msn_contact_add_request_done(int fd, int error, int res, void *data)
{
	msn_addr_book_t *ab = data;

	if(error) {
		error_log(errno, "failed to send contact list request\n");
		msn_addr_book_free(ab);
		return;
	}
	assert(ab);
	debug_log(9, "fd %d, %d bytes send finish\n", fd, res);
	sock_read(ab->ev, ab->fd, msn_contact_add_filter, ab);
	
}

static int msn_contact_add_filter(const char *buf, const int size, void *data)
{
	msn_addr_book_t *ab = data;
	char *path;

	assert(ab);
	if(!buf) {
		/* read finish */
		msn_addr_book_free(ab);
		return;
	}
	path = path_get(PREFIX"/share/tmp.xml");
	ab->in += size;
	debug_log(9, "%d bytes in, total in %d\n", size, ab->in);
	if(!ab->out) {
		ab->out = fopen(path, "w");
		if(!ab->out) {
			error_log(errno, "failed to open %s\n", path);
		}
	}
	assert(ab->out);
	if(!ab->header_parsed) {
		ab->rh = http_reply_header_parse(buf, size);
		assert(ab->rh);
		ab->header_size = headers_end(buf, size);
		ab->header_parsed = 1;
		header_entry_t *en = header_entry_get_by_id(ab->rh->headers, HDR_CONTENT_LENGTH);
		if(en) {
			ab->total = ab->header_size + atoi(en->value);
		}
		debug_log(6, "Total %d, in %d, header size %d\n", ab->total, ab->in, ab->header_size);
		if(ab->rh->statu != HTTP_OK) {
			debug_log(1, "sorry! %s response %d\n", contact_domain, ab->rh->statu);
		}
		if(1 != fwrite(buf + ab->header_size, size - ab->header_size, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	else {
		if(1 != fwrite(buf, size, 1, ab->out)) {
			debug_log(0, "%s", "fwrite failed\n");
		}
	}
	if(ab->in >= ab->total) 
		msn_addr_book_free(ab);
}

